#include "OgreGLESHardwareBufferManager.h"
#include "OgreGLESHardwareVertexBuffer.h"
#include "OgreException.h"
#include "OgreLogManager.h"

namespace Ogre
{
	GLESHardwareVertexBuffer::GLESHardwareVertexBuffer(HardwareBufferManagerBase* mgr,
													   size_t vertexSize,
													   size_t numVertices,
													   HardwareBuffer::Usage usage,
													   bool useShadowBuffer)
		: HardwareVertexBuffer(mgr, vertexSize, numVertices, usage, false, useShadowBuffer)
	{
		if (!useShadowBuffer)
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
						"Only support with shadowBuffer",
						"GLESHardwareVertexBuffer");
		}

		glGenBuffers(1, &mBufferId);
		GL_CHECK_ERROR;

		if (!mBufferId)
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
					    "Cannot create GL vertex buffer",
						"GLESHardwareVertexBuffer::GLESHardwareVertexBuffer");
		}

		glBindBuffer(GL_ARRAY_BUFFER, mBufferId);
		GL_CHECK_ERROR;
		glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, NULL,
			GLESHardwareBufferManager::getGLUsage(usage));
		GL_CHECK_ERROR;
	}

	GLESHardwareVertexBuffer::~GLESHardwareVertexBuffer()
	{
		glDeleteBuffers(1, &mBufferId);
		GL_CHECK_ERROR;
	}

	void* GLESHardwareVertexBuffer::lockImpl(size_t offset,
											size_t length,
											LockOptions options)
	{
		if (mIsLocked)
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
						"Invalid attempt to lock an index buffer that has already been locked",
						"GLESHardwareVertexBuffer::lock");
		}

		void* retPtr = 0;

		if (length < OGRE_GL_MAP_BUFFER_THRESHOLD)
		{
			retPtr = static_cast<GLESHardwareBufferManager*>(
				HardwareBufferManager::getSingletonPtr())->allocateScratch((uint32)length);
			if (retPtr)
			{
				mLockedToScratch = true;
				mScratchOffset = offset;
				mScratchSize = length;
				mScratchPtr = retPtr;
				mScratchUploadOnUnlock = (options != HBL_READ_ONLY);

				if (options != HBL_DISCARD)
				{
					readData(offset, length, retPtr);
				}
			}
		}
		else
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
						"Invalid Buffer lockSize",
						"GLESHardwareVertexBuffer::lock");
		}

		return retPtr;
	}

	void GLESHardwareVertexBuffer::unlockImpl(void)
	{
		if (mLockedToScratch)
		{
			if (mScratchUploadOnUnlock)
			{
				writeData(mScratchOffset, mScratchSize, mScratchPtr,
					mScratchOffset == 0 && mScratchSize == getSizeInBytes());
			}

			static_cast<GLESHardwareBufferManager*>(
				HardwareBufferManager::getSingletonPtr())->deallocateScratch(mScratchPtr);

			mLockedToScratch = false;
		}
		else
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
				"Only locking to scratch is supported",
				"GLESHardwareVertexBuffer::unlockImpl");
		}
	}

	void GLESHardwareVertexBuffer::readData(size_t offset, size_t length,
		void* pDest)
	{
		if (mUseShadowBuffer)
		{
			void* srcData = mpShadowBuffer->lock(offset, length, HBL_READ_ONLY);
			memcpy(pDest, srcData, length);
			mpShadowBuffer->unlock();
		}
		else
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
				"Read hardware buffer is not supported",
				"GLESHardwareVertexBuffer::readData");
		}
	}

	void GLESHardwareVertexBuffer::writeData(size_t offset,
											 size_t length,
											 const void* pSource,
											 bool discardWholeBuffer)
	{
		glBindBuffer(GL_ARRAY_BUFFER, mBufferId);
		GL_CHECK_ERROR;

		if (mUseShadowBuffer)
		{
			void* destData = mpShadowBuffer->lock(offset, length,
											discardWholeBuffer ? HBL_DISCARD : HBL_NORMAL);
			memcpy(destData, pSource, length);
			mpShadowBuffer->unlock();
		}

		if (offset == 0 && length == mSizeInBytes)
		{
			glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, pSource,
				GLESHardwareBufferManager::getGLUsage(mUsage));
			GL_CHECK_ERROR;
		}
		else
		{
			if (discardWholeBuffer)
			{
				glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, NULL,
					GLESHardwareBufferManager::getGLUsage(mUsage));
			}

			glBufferSubData(GL_ARRAY_BUFFER, offset, length, pSource);
			GL_CHECK_ERROR;
		}
	}

	void GLESHardwareVertexBuffer::_updateFromShadow(void)
	{
		if (mUseShadowBuffer && mShadowUpdated && !mSuppressHardwareUpdate)
		{
			const void* srcData = mpShadowBuffer->lock(mLockStart,
													   mLockSize,
													   HBL_READ_ONLY);

			glBindBuffer(GL_ARRAY_BUFFER, mBufferId);
			GL_CHECK_ERROR;

			if (mLockStart == 0 && mLockSize == sizeInBytes)
			{
				glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, srcData,
					GLESHardwareBufferManager::getGLUsage(mUsage));
				GL_CHECK_ERROR;
			}
			else
			{
				glBufferSubData(GL_ARRAY_BUFFER, mLockStart, mLockSize, srcData);
				GL_CHECK_ERROR;
			}

			mpShadowBuffer->unlock();
			mShadowUpdated = false;
		}
	}
}
